namespace Angelus.Data.Linq.OracleClient
{
	using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.OracleClient;
    using System.Expressions;
    using System.Data.DLinq.ProviderBase;
    using System.Query;
    using Angelus.Data.Linq.ProviderBase;

    /// <summary>
    /// 
    /// </summary>
    internal abstract class OracleQuerySession : ReadSession
    {
        #region Query Session Implementation

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        internal abstract int ExecuteNonQuery();

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        internal abstract IEnumerator ExecuteQuery();

        #endregion
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal class OracleQuerySession<T> : OracleQuerySession
    {
        #region Fields

        private readonly OracleContext _context;
        private readonly QueryInfo[] _queries;
        private readonly Expression _query;
        private readonly RowReader[] _readers;
        private readonly ResultReader<T> _resultReader;

        #endregion

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="resultReader"></param>
        /// <param name="reader"></param>
        internal OracleQuerySession(OracleContext context, ResultReader<T> resultReader, RowReader reader)
            : this(context, null, new QueryInfo[0], resultReader)
        {
            this._readers = new RowReader[] {reader};
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="queries"></param>
        /// <param name="resultReader"></param>
        /// <param name="reader"></param>
        internal OracleQuerySession(OracleContext context, QueryInfo[] queries, ResultReader<T> resultReader,
                                    IDataReader reader)
            : this(context, null, queries, resultReader)
        {
            this._readers[0] = new OracleCompletingRowReader(this._context.ConnectionManager, new DataRowReader(reader));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="query"></param>
        /// <param name="queries"></param>
        /// <param name="resultReader"></param>
        internal OracleQuerySession(OracleContext context, Expression query, QueryInfo[] queries,
                                    ResultReader<T> resultReader)
        {
            this._context = context;
            this._query = query;
            this._queries = queries;
            this._resultReader = resultReader;
            this._readers = new RowReader[this._queries.Length];
        }

        #endregion

        #region Query Session Implementation

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        internal override int ExecuteNonQuery()
        {
            QueryInfo info = this._queries[0];
            OracleConnection connection =
                (OracleConnection) this._context.ConnectionManager.UseConnection(this._context);
            OracleCommand command = connection.CreateCommand();
            command.CommandText = info.CommandText;
            command.Transaction = (OracleTransaction) this._context.LocalTransaction;
            AssignParameters(command, info.Parameters);

            if (null != this._context.Log)
            {
                this._context.Log.WriteLine(info.CommandText);
                this._context.Log.WriteLine();
            }
            Console.WriteLine(info.CommandText);
            this._context.QueryCount++;
            int result = command.ExecuteNonQuery();
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        internal override IEnumerator ExecuteQuery()
        {
            string text;
            IEnumerable<T> enumerable = new OracleQueryEnumerable(this, this.GetReader(0), this._resultReader);
            MethodCallExpression expression = this._query as MethodCallExpression;
            if (((null != expression) &&
                 ((typeof (Queryable) == expression.Method.DeclaringType) ||
                  (typeof (Sequence) == expression.Method.DeclaringType))) && ((text = expression.Method.Name) != null))
            {
                if ("First" == text)
                {
                    enumerable = new T[] {enumerable.First()};
                }
                else if ("FirstOrDefault" == text)
                {
                    enumerable = new T[] {enumerable.FirstOrDefault()};
                }
                else if ("Single" == text)
                {
                    enumerable = new T[] {enumerable.Single()};
                }
                else if ("SingleOrDefault" == text)
                {
                    enumerable = new T[] {enumerable.SingleOrDefault()};
                }
            }
            return enumerable.GetEnumerator();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        internal override RowReader GetReader(int index)
        {
            if (null == this._readers[index])
            {
                this._readers[index] = this.ExecuteReader(index);
            }
            return this._readers[index];
        }

        #endregion

        #region Private Implementation

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="parameters"></param>
        private static void AssignParameters(OracleCommand command, IEnumerable<SqlParameterInfo> parameters)
        {
            foreach (SqlParameterInfo info in parameters)
            {
                OracleProviderType type = (OracleProviderType) info.Parameter.SqlType;

                OracleParameter parameter = command.CreateParameter();
                parameter.ParameterName = info.Parameter.Name;
                parameter.OracleType = type.OracleType;

                if (parameter.OracleType == OracleType.Cursor)
                {
                    parameter.Direction = ParameterDirection.Output;
                }
                else
                {
                    object value = info.Expression.Eval();
                    parameter.Value = OracleProviderType.GetParameterData(type.OracleType, value);
                }
                command.Parameters.Add(parameter);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void DisposeAll()
        {
            foreach (RowReader reader in this._readers)
            {
                IDisposable disposable = reader as IDisposable;
                if (null != disposable)
                {
                    disposable.Dispose();
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        private RowReader ExecuteReader(int index)
        {
            RowReader reader;
            QueryInfo info = this._queries[index];
            OracleConnection connection =
                (OracleConnection) this._context.ConnectionManager.UseConnection(this._context);
            try
            {
                OracleCommand command = connection.CreateCommand();
                command.CommandText = info.CommandText;
                command.Transaction = (OracleTransaction) this._context.LocalTransaction;
                AssignParameters(command, info.Parameters);

                if (null != this._context.Log)
                {
                    this._context.Log.WriteLine(info.CommandText);
                    this._context.Log.WriteLine();
                }
                Console.WriteLine(info.CommandText);

                this._context.QueryCount++;
                OracleCompletingRowReader rowReader = new OracleCompletingRowReader(this._context.ConnectionManager,
                                                                                    new DataRowReader(
                                                                                        command.ExecuteReader()));
                this._context.ConnectionManager.UseConnection(rowReader);
                reader = rowReader;
            }
            finally
            {
                this._context.ConnectionManager.ReleaseConnection(this._context);
            }
            return reader;
        }

        #endregion

        #region Nested classes

        /// <summary>
        /// 
        /// </summary>
        private class OracleQueryEnumerable : IEnumerable<T>
        {
            #region Fields

            private readonly RowReader _reader;
            private readonly ResultReader<T> _resultReader;
            private readonly OracleQuerySession<T> _session;

            #endregion

            #region Constructors

            /// <summary>
            /// 
            /// </summary>
            /// <param name="session"></param>
            /// <param name="reader"></param>
            /// <param name="resultReader"></param>
            internal OracleQueryEnumerable(OracleQuerySession<T> session, RowReader reader, ResultReader<T> resultReader)
            {
                this._session = session;
                this._reader = reader;
                this._resultReader = resultReader;
            }

            #endregion

            #region IEnumerable Implementation

            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            public IEnumerator<T> GetEnumerator()
            {
                return new OracleQueryEnumerator(this);
            }

            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            #endregion

            #region Nested classes

            /// <summary>
            /// 
            /// </summary>
            private class OracleQueryEnumerator : IEnumerator<T>, IDelayDisposable
            {
                #region Fields

                private readonly OracleQueryEnumerable _enumerable;
                private T _current;
                private bool _delayDispose;
                private bool _hasValue;

                #endregion

                #region Constructors

                /// <summary>
                /// 
                /// </summary>
                /// <param name="enumerable"></param>
                internal OracleQueryEnumerator(OracleQueryEnumerable enumerable)
                {
                    this._enumerable = enumerable;
                }

                #endregion

                #region Properties

                /// <summary>
                /// 
                /// </summary>
                public bool DelayDispose
                {
                    get { return this._delayDispose; }
                    set { this._delayDispose = value; }
                }

                #endregion

                #region IDisposable Implementation

                /// <summary>
                /// 
                /// </summary>
                public void Dispose()
                {
                    if (!this._delayDispose)
                    {
                        this._enumerable._session.DisposeAll();
                    }
                }

                #endregion

                #region IEnumerator Implementation

                /// <summary>
                /// 
                /// </summary>
                public T Current
                {
                    get
                    {
                        if (!this._hasValue)
                        {
                            this._current = this._enumerable._resultReader.Read(this._enumerable._session,
                                                                                this._enumerable._reader);
                            this._hasValue = true;
                        }
                        return this._current;
                    }
                }

                /// <summary>
                /// 
                /// </summary>
                object IEnumerator.Current
                {
                    get { return this.Current; }
                }

                /// <summary>
                /// 
                /// </summary>
                /// <returns></returns>
                public bool MoveNext()
                {
                    this._hasValue = false;
                    return this._enumerable._reader.Read();
                }

                /// <summary>
                /// 
                /// </summary>
                void IEnumerator.Reset()
                {
                }

                #endregion
            }

            #endregion
        }

        #endregion
    }
}